from salsa.coresvc.config import Config

from salsa.coresvc.logger import LoggerService
from salsa.util import LoggerSeverity
from salsa.coresvc.taskmgr import TaskManager

from salsa.util.io import IOMaster, RPCEncoderDecoder, IOMasterDead
from salsa.util.io import IOSlave, IOException, IOSlaveCommunicator
from salsa.util.rpc import RPCInfo, RPCDispatcher, RPCClient

import os
import sys 
import shutil
import signal

from threading import Thread

import salsa
from salsa import trace_exec_flow, do_trace, do_trace_interrupt, SALSAException

class SALSACoreException(SALSAException):
    pass

DEFAULT_SALSA_CONFIG  = '/pool/leo4-devel/salsa/salsam/salsa/conf/config.xml'
CORE_JOIN_TIMEOUT = 5

class Core:
    def __init__(self):
        self.read_env()
        
        salsa.core = self
        
        self.config = Config(self.config_path)
        self.globals = self.config.get_globals()
        
        self.core_port = int(self.globals['core_port'])
        self.core_host = self.globals['core_host']
        self.init_root_dirs()
        
        self.io_master = IOMaster(port = self.core_port, 
                                  host =  self.core_host, 
                                  enc_dec = RPCEncoderDecoder()) 
        
        self.logger = LoggerService()
        self.taskmgr = TaskManager()
        
        salsa.logger = self.logger
        
        self.init_signals()
        
        self.create_rpc_dispatcher()
        
        self.is_done = False
        
    def read_env(self):
        self.config_path = os.getenv('SALSA_CONFIG', DEFAULT_SALSA_CONFIG)
        
        if not self.config_path:
            raise SALSACoreException('Environment variable SALSA_CONFIG is not set')
        if not os.path.exists(self.config_path):
            raise SALSACoreException('SALSA config %s is not exists' % self.config_path)
    
    def init_root_dirs(self):
        self.data_dir = self.globals['data_dir']
        
        if not os.path.exists(self.data_dir):
            try:
                os.makedirs(self.data_dir)
            except OSError as ose:
                raise SALSACoreException('OS error occurred when attempting to create data_dir %s (%s)' % (self.data_dir, str(ose)))
        
        self.bin_dir = self.globals['bin_dir']
        
        if not os.path.exists(self.bin_dir):
            raise SALSACoreException('SALSA binaries directory %s does not exist' % self.bin_dir)
    
    def create_rpc_dispatcher(self):
        self.rpc_disp = RPCDispatcher(self.rpc_info)
        self.rpc_disp.bind(self, self.logger, self.taskmgr)
    
    def init_signals(self):
        signal.signal(signal.SIGINT, self.interrupt)
        signal.signal(signal.SIGTERM, self.interrupt)
    
    def abs_data_path(self, path = '', cleanup = False):
        if not os.path.isabs(path):
            data_path = os.path.join(self.data_dir, path)
            data_dir = os.path.dirname(data_path)
            
            if not os.path.exists(data_dir):
                os.makedirs(data_dir)
            
            if cleanup:
                shutil.rmtree(data_dir)
                os.makedirs(data_dir)
            
            return data_path
        
        return path
   
    def abs_bin_path(self, path = ''):
        if not os.path.isabs(path):
            return os.path.join(self.bin_dir, path)
        
        return path
    
    def start(self):
        self.io_master.start()
        
        print >> sys.stderr, 'Salsa Core service is listening on %s:%s' % (self.io_master.host, self.io_master.port)
        
        self.core_thread = Thread(target = self._run, name = 'CoreThread')
        self.core_thread.setDaemon(True)              
        self.core_thread.start()
        
        if trace_exec_flow: do_trace('Core is started')
        
        self.taskmgr.start()
    
    def interrupt(self, signum, stack):
        if trace_exec_flow: do_trace('Core is interrupted')
        do_trace_interrupt()
        
        self.stop()
    
    def join(self):
        while not self.is_done:
            self.core_thread.join(CORE_JOIN_TIMEOUT)
    
    def stop(self):
        if not self.is_done:        
            salsa.stopping = True
               
            self.taskmgr.stop()
            
            self.is_done = True
            self.io_master.stop()
            
            if trace_exec_flow: do_trace('Core is stopped')
    
    def fail(self, message):
        # TODO: Dump
        
        self.logger.log(LoggerSeverity.LS_CRITICAL, 'Core is dead!')
        self.logger.log(LoggerSeverity.LS_CRITICAL, 'Reason: %s' % message)
        
        print >> sys.stderr, 'Core is dead!'
        print >> sys.stderr, 'Reason: %s' % message
        
        self.stop()
        
    def _run(self):
        io_master = self.io_master
        
        while not self.is_done:
            try:
                io_request = io_master.get()  
                                     
                request = io_request.get_blocked()
                
                reply = self.rpc_disp.process(request)
                    
                io_request.put(reply)
            except IOMasterDead:
                break
        
        self.stop()

# Create RPC protocol for Core
Core.rpc_info = RPCInfo()
Core.rpc_info.register(Core, 'stop')
Core.rpc_info.register(Core, 'abs_data_path')
Core.rpc_info.register(LoggerService, 'log')
Core.rpc_info.register(TaskManager, 'create_task', 'attach_tasks')
Core.rpc_info.register(TaskManager, 'get_task_info')
Core.rpc_info.register(TaskManager, 'list_services', 'list_tasks')

class CoreClient:
    def __init__(self):
        self.core_port = int(os.getenv('SALSA_CORE_PORT', 4010))
        self.core_host = os.getenv('SALSA_HOST', 'localhost')
        
        try:
            self.core_io_slave = IOSlave(host = self.core_host, port = self.core_port, 
                                         enc_dec = RPCEncoderDecoder())
            self.core_io_slave.start()
            
            self.core = RPCClient(Core.rpc_info, IOSlaveCommunicator(self.core_io_slave))
            
            if not salsa.core:
                salsa.core = self.core
        except IOException as e:
            raise SALSACoreException("Couldn't connect to Core", str(e))
    
    def __del__(self):
        self.stop()
    
    def stop(self):
        self.core_io_slave.stop()